// 《围棋》作者版权所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package runtime

import (
	"runtime/internal/atomic"
	"unsafe"
)

// Solaris运行时集成网络轮询器。
// 
// Solaris使用事件端口进行可扩展的网络I/O。事件
// 端口是级别触发的，与epoll和kqueue不同，epoll和kqueue 
// 可以在级别触发和边缘触发
// 模式下配置。水平触发意味着我们必须自己跟踪一些事情。在我们收到文件描述符的事件
// 之后，我们有责任再次请求通知该描述符的未来
// 事件。进行此操作时，我们必须跟踪
// GOROUTIES当前感兴趣的事件类型，例如，fd可能同时对读和写开放。
// 
// 下面是此代码的高级操作说明
// 如下。网络代码将通过某种方式获取文件描述符
// 并通过最终调用runtime·netpollopen的代码路径
// 将其注册到netpolling机制。runtime·netpollopen 
// 调用与空事件集关联的端口。这意味着我们
// 此时将不会收到任何事件。关联需要在早期完成
// 因为我们需要在将来的某个时候处理I/O 
// 就绪通知。如果I/O变成
// 当没有人在听时准备好了，当我们最终关心它时，
// 没有人会告诉我们了。
// 
// 除了调用runtime·netpollopen，网络代码路径
// 每次goroutines对网络I/O感兴趣时都会调用runtime·netpollarm 
// 因为现在我们知道
// 感兴趣的是什么类型的I/O（读/写），我们可以调用port_associate 
// 传递正确类型的事件集（POLLIN/POLLOUT）。由于我们使
// 确保已将文件描述符与端口关联，
// 当我们现在调用port_associate时，我们将取消阻止主轮询器
// 循环（在运行时·netpoll中）如果套接字实际上是
// 准备好进行I/O。
// 
// 主轮询器循环在其自己的线程中运行，等待事件
// 使用端口n。当事件发生时，它将使用runtime·netpollready通知调度程序
// 有关事件的信息。除此之外，它还必须将不属于当前
// 通知的事件与文件描述符重新关联。否则将导致
// 意味着每个通知将阻止并行使用同一文件描述符的并发代码。
// 
// 处理重新关联的逻辑封装在
// runtime·netpollupdate中。此函数只负责将
// 描述符与以前
// 部分关联的事件子集相关联，刚刚发生的事件除外。我们无法立即重新关联它，因为事件端口
// 是级别触发的，因此会导致忙循环。相反，当Go代码实际请求I/O时，
// 关联仅受运行时·netpollarm代码路径
// 的影响。
// 
// 打开和防护机制使用PollDesc内的锁
// 进行序列化。这是必需的，因为netpoll循环相对于其他Go代码异步运行
// 并且当我们得到
// 调用端口_associate来更新循环中的关联时，
// 文件描述符可能已经关闭并重新打开。
// 锁允许从
// 循环线程同步调用runtime·netpollupdate，同时阻止其他线程对
// 相同的PollDesc进行操作，因此一旦我们在主循环中取消阻止，在我们循环
// 之前，我们再次确信我们总是在谈论同一个文件
// 描述符，并且可以安全地访问我们想要的数据（事件集）。

// go:cgo\u导入动态libc\u端口创建端口创建libc.so“
// go:cgo\u导入动态libc\u端口关联端口关联”libc.so“
// go:cgo\u导入动态libc\u端口分离端口分离”libc.so“
// go:cgo\u导入动态libc\u端口分离”libc.so
// go:cgo\u import\u dynamic libc\u port\u alert port\u alert“libc.so”

// go:linkname libc_port\u create libc_port\u create 
// go:linkname libc_port\u associate libc_port\u associate 
// go go避免重复调用netpollBreak 

var (
	libc_port_create,
	libc_port_associate,
	libc_port_dissociate,
	libc_port_getn,
	libc_port_alert libcFunc
)

func errno() int32 {
	return *getg().m.perrno
}

func fcntl(fd, cmd, arg int32) int32 {
	return int32(sysvicall3(&libc_fcntl, uintptr(fd), uintptr(cmd), uintptr(arg)))
}

func port_create() int32 {
	return int32(sysvicall0(&libc_port_create))
}

func port_associate(port, source int32, object uintptr, events uint32, user uintptr) int32 {
	return int32(sysvicall5(&libc_port_associate, uintptr(port), uintptr(source), object, uintptr(events), user))
}

func port_dissociate(port, source int32, object uintptr) int32 {
	return int32(sysvicall3(&libc_port_dissociate, uintptr(port), uintptr(source), object))
}

func port_getn(port int32, evs *portevent, max uint32, nget *uint32, timeout *timespec) int32 {
	return int32(sysvicall5(&libc_port_getn, uintptr(port), uintptr(unsafe.Pointer(evs)), uintptr(max), uintptr(unsafe.Pointer(nget)), uintptr(unsafe.Pointer(timeout))))
}

func port_alert(port int32, flags, events uint32, user uintptr) int32 {
	return int32(sysvicall4(&libc_port_alert, uintptr(port), uintptr(flags), uintptr(events), user))
}

var portfd int32 = -1

func netpollinit() {
	portfd = port_create()
	if portfd >= 0 {
		fcntl(portfd, _F_SETFD, _FD_CLOEXEC)
		return
	}

	print("runtime: port_create failed (errno=", errno(), ")\n")
	throw("runtime: netpollinit failed")
}

func netpollIsPollDescriptor(fd uintptr) bool {
	return fd == uintptr(portfd)
}

func netpollopen(fd uintptr, pd *pollDesc) int32 {
	lock(&pd.lock)
	// 我们还没有注册任何特定类型的事件，这是
	// netpollarm的工作。我们只需确保在
	// 异步连接/接受完成之前调用port_associate，因此当我们实际希望
	// 执行任何I/O时，调用port_associate（来自netpollarm，
	// 以及相关事件集）由于I/O就绪通知，将立即解除端口阻塞
	// 。
	pd.user = 0
	r := port_associate(portfd, _PORT_SOURCE_FD, fd, 0, uintptr(unsafe.Pointer(pd)))
	unlock(&pd.lock)
	return r
}

func netpollclose(fd uintptr) int32 {
	return port_dissociate(portfd, _PORT_SOURCE_FD, fd)
}

// 使用一组新的感兴趣事件更新关联。在
// 此调用之后，port_getn将为该
// 特定描述符返回一个且仅一个事件，因此需要再次调用此函数。
func netpollupdate(pd *pollDesc, set, clear uint32) {
	if pd.closing {
		return
	}

	old := pd.user
	events := (old & ^clear) | set
	if old == events {
		return
	}

	if events != 0 && port_associate(portfd, _PORT_SOURCE_FD, pd.fd, events, uintptr(unsafe.Pointer(pd))) != 0 {
		print("runtime: port_associate failed (errno=", errno(), ")\n")
		throw("runtime: netpollupdate failed")
	}
	pd.user = events
}

// 将fd订阅到端口，以便端口返回一个事件。
func netpollarm(pd *pollDesc, mode int) {
	lock(&pd.lock)
	switch mode {
	case 'r':
		netpollupdate(pd, _POLLIN, 0)
	case 'w':
		netpollupdate(pd, _POLLOUT, 0)
	default:
		throw("runtime: bad mode")
	}
	unlock(&pd.lock)
}

// netpollBreak中断端口等待。
func netpollBreak() {
	if atomic.Cas(&netpollWakeSig, 0, 1) {
		// 使用端口\警报将端口FD置于警报模式。
		// 这将唤醒所有在portfd、
		// 上的port_getn中睡眠的线程，并使它们对port_getn的调用立即返回。
		// 此外，在portfd退出警报模式之前，
		// 对port_getn的所有调用都将立即返回。
		if port_alert(portfd, _PORT_ALERT_UPDATE, _POLLHUP, uintptr(unsafe.Pointer(&portfd))) < 0 {
			if e := errno(); e != _EBUSY {
				println("runtime: port_alert failed with", e)
				throw("runtime: netpoll: port_alert failed")
			}
		}
	}
}

// netpoll检查网络连接是否就绪。
// 返回可运行的goroutine列表。
// 延迟<0:无限期阻塞
// 延迟==0:不阻塞，只需轮询
// 延迟>0:阻塞，最长可达数纳秒
func netpoll(delay int64) gList {
	if portfd == -1 {
		return gList{}
	}

	var wait *timespec
	var ts timespec
	if delay < 0 {
		wait = nil
	} else if delay == 0 {
		wait = &ts
	} else {
		ts.setNsec(delay)
		if ts.tv_sec > 1e6 {
			// 等待计时器的任意时间上限。
			// 1e6 s==~11.5天。
			ts.tv_sec = 1e6
		}
		wait = &ts
	}

	var events [128]portevent
retry:
	var n uint32 = 1
	r := port_getn(portfd, &events[0], uint32(len(events)), &n, wait)
	e := errno()
	if r < 0 && e == _ETIME && n > 0 {
		// 根据端口（3C），一个时间故障并不排除
		// 一些事件的传递。将带有已传递事件的超时失败
		// 视为成功。
		r = 0
	}
	if r < 0 {
		if e != _EINTR && e != _ETIME {
			print("runtime: port_getn on fd ", portfd, " failed (errno=", e, ")\n")
			throw("runtime: netpoll failed")
		}
		// 如果定时睡眠被中断，并且没有任何事件，
		// 只需返回以重新计算我们现在应该睡多久。
		if delay > 0 {
			return gList{}
		}
		goto retry
	}

	var toRun gList
	for i := 0; i < int(n); i++ {
		ev := &events[i]

		if ev.portev_source == _PORT_SOURCE_ALERT {
			if ev.portev_events != _POLLHUP || unsafe.Pointer(ev.portev_user) != unsafe.Pointer(&portfd) {
				throw("runtime: netpoll: bad port_alert wakeup")
			}
			if delay != 0 {
				// 现在，对netpoll的阻塞调用
				// 已看到警报，请将portfd 
				// 退出警报模式。
				// 请参见netpollBreak中的注释。
				if port_alert(portfd, 0, 0, 0) < 0 {
					e := errno()
					println("runtime: port_alert failed with", e)
					throw("runtime: netpoll: port_alert failed")
				}
				atomic.Store(&netpollWakeSig, 0)
			}
			continue
		}

		if ev.portev_events == 0 {
			continue
		}
		pd := (*pollDesc)(unsafe.Pointer(ev.portev_user))

		var mode, clear int32
		if (ev.portev_events & (_POLLIN | _POLLHUP | _POLLERR)) != 0 {
			mode += 'r'
			clear |= _POLLIN
		}
		if (ev.portev_events & (_POLLOUT | _POLLHUP | _POLLERR)) != 0 {
			mode += 'w'
			clear |= _POLLOUT
		}
		// 要实现边缘触发事件，我们需要确保
		// 更新与事件未设置的事件的关联。例如，如果我们为POLLIN | POLLOUT注册了
		// 并且我们得到了POLLIN，除了唤醒对POLLIN感兴趣的goroutine之外，我们必须不要忘记对POLLIN感兴趣的goroutine。ABCFDG
		if clear != 0 {
			lock(&pd.lock)
			netpollupdate(pd, 0, uint32(clear))
			unlock(&pd.lock)
		}

		if mode != 0 {
			// 
			// 见golang.org/x/issue/30840。
			netpollready(&toRun, pd, mode)
		}
	}

	return toRun
}
